package com.zh.webcommon.common.support.zip;

import org.apache.commons.compress.archivers.zip.Zip64Mode;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Enumeration;

import static com.zh.mycommon.util.MyFilenameUtil.downloadsDirectory;

/**
 * 文件压缩工具类
 * <p>
 * 使用Apache的ZipArchiveOutputStream
 */
public final class MyZipUtils {
    public static void main(String[] args) throws IOException {
        Unzip(new File("D:\\滁州二期.zip"), downloadsDirectory().toString()); //OK
        Zip(downloadsDirectory().toString(), Paths.get(downloadsDirectory().toString(), "滁州二期"));
    }

    /**
     * 压缩文件为ZIP文件
     * 使用Apache的ZipArchiveOutputStream
     *
     * @param destDir 存放ZIP文件的位置
     * @param sources 要压缩的文件
     */
    public static Path Zip(String destDir, Path... sources) throws IOException {
        if (sources.length == 0) return null;

        Path destPath = Path.of(destDir).toAbsolutePath();
        if (Files.notExists(destPath)) {
            Files.createDirectories(destPath); // 确保目录存在且创建所有父目录
        }

        // 生成ZIP文件名。单个目录-使用目录名；多个文件或目录-使用output名；
        String zipFileName;
        if (sources.length == 1 && Files.isDirectory(sources[0])) {
            zipFileName = sources[0].getFileName() + "_" + System.currentTimeMillis() + ".zip";
        } else {
            zipFileName = "archive_" + System.currentTimeMillis() + ".zip";
        }
        //生成ZIP文件的路径
        Path zipFilePath = destPath.resolve(zipFileName);
        Path tempZipFilePath = destPath.resolve(zipFileName + ".part");

        //确保资源正确关闭
        try (ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(Files.newOutputStream(tempZipFilePath));) {
            // 启用ZIP64和UTF-8支持
            zipOut.setUseZip64(Zip64Mode.AsNeeded);
            zipOut.setEncoding("UTF-8"); // 支持中文文件名

            // 压缩每个源文件/目录
            for (Path source : sources) {
                if (Files.exists(source)) {
                    zipFileOrDirectory(source.toFile(), zipOut, "");
                } else {
                    System.err.println("源文件/目录不存在: " + source);
                }
            }
        }
        // 压缩完成后，将临时文件重命名为正式ZIP文件
        try {
            Files.move(tempZipFilePath, zipFilePath, StandardCopyOption.REPLACE_EXISTING);

            System.out.println("压缩完成: " + zipFilePath);
            return zipFilePath;
        } catch (IOException e) {
            System.err.println("文件重命名为正式ZIP文件时出错. err=" + e.getMessage());
            zipFilePath.toFile().delete();
            return null;
        }
    }

    /**
     * 递归压缩单个文件或目录
     *
     * @param source     源文件/目录路径
     * @param zipOut     ZIP输出流
     * @param parentPath ZIP包内的父路径
     * @throws IOException 当压缩过程中发生I/O错误时抛出
     */
    private static void zipFileOrDirectory(File source, ZipArchiveOutputStream zipOut, String parentPath) throws IOException {
        String entryName = Paths.get(parentPath, source.getName()).toString().replace('\\', '/');
//        System.out.println(String.format("处理%s ：entryName = %s", source.isFile() ? "文件" : "目录", entryName + (source.isDirectory() ? "/" : "")));

        // 处理文件
        if (source.isFile()) {
            ZipArchiveEntry archiveEntry = zipOut.createArchiveEntry(source, entryName);
            archiveEntry.setTime(source.lastModified());
            zipOut.putArchiveEntry(archiveEntry);

            try (var input = Files.newInputStream(source.toPath())) {
                input.transferTo(zipOut);
            } finally {
                zipOut.closeArchiveEntry(); // 确保条目关闭，即使发生异常
            }
        } else { // 处理目录
            ZipArchiveEntry archiveEntry = zipOut.createArchiveEntry(source, entryName + "/");
            archiveEntry.setTime(source.lastModified());
            zipOut.putArchiveEntry(archiveEntry);
            zipOut.closeArchiveEntry();

            // 递归处理子文件/目录
            File[] children = source.listFiles();
            if (children == null) return;
            for (File child : children) {
                zipFileOrDirectory(child, zipOut, entryName);
            }
        }
    }


    /**
     * zip文件解压
     *
     * @param file      文件
     * @param targetDir 解压的目标文件夹
     * @throws IOException
     */
    public static void Unzip(File file, String targetDir) throws IOException {
        if (!file.exists()) {
            throw new IllegalArgumentException("file 不存在");
        }
        Path outDir = Path.of(targetDir).toAbsolutePath();
        if (Files.notExists(outDir)) {
            Files.createDirectories(outDir); // 确保目录存在且创建所有父目录
        }

        try (ZipFile zipFile = ZipFile.builder().setFile(file).get()) {
            Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();

            while (entries.hasMoreElements()) {
                ZipArchiveEntry entry = entries.nextElement();

                // 安全检查：防止路径遍历攻击
                Path entryPath = outDir.resolve(entry.getName()).normalize();
                if (!entryPath.startsWith(outDir)) {
                    System.err.println("恶意条目: " + entry.getName());
                    return;
                }

                if (entry.isDirectory()) {
                    Files.createDirectories(entryPath); // 创建完整目录路径
                } else {
                    // 确保父目录存在
                    Files.createDirectories(entryPath.getParent());

                    try (InputStream in = zipFile.getInputStream(entry); OutputStream out = Files.newOutputStream(entryPath)) {
                        in.transferTo(out);
                    }
                }
            }
        } finally {
            System.out.println("解压完成 outDir: " + outDir);
        }
    }

}